/*
 * Copyright (c) 2012, Alex Gittemier, All Rights Reserved.
 *
 *  Simple Xml is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Simple Xml is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Simple Xml.  If not, see <http://www.gnu.org/licenses/>.
 */
package gbc.xml.simple;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * The class used to parse xml.
 * <p>
 * <b>Example:</b> List&lt;XmlTag> xml = new XmlParser().parse(url);
 */
public class XmlParser {
	// private final String LOG_TAG = this.getClass().getName();

	private List<XmlTag> xmlDoc;
	private List<XmlTag> xmlStack;
	
	/**
	 * The class used to parse xml.
	 * <p>
	 * <b>Example:</b> List&lt;XmlTag> xml = new XmlParser().parse(url);
	 */
	public XmlParser () {
	}

	/**
	 * Parses the specified XML document and returns a List representation of
	 * the document. This method is a blocking operation, so it is highly
	 * recommended to run this in separate thread.
	 * 
	 * @param url
	 *            A URL specifying the location of the XML document.
	 * @return A list format of the XML document.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws SAXException
	 *             any SAX related exception
	 */
	public List<XmlTag> parse(String url) throws IOException, SAXException {
		// Initialize objects
		this.xmlDoc = new ArrayList<XmlTag>();
		this.xmlStack = new ArrayList<XmlTag>();

		// start parsing
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			factory.setNamespaceAware(true);
			SAXParser parser;
			parser = factory.newSAXParser();
			parser.parse(url, new XmlParserHandler());
		} catch (ParserConfigurationException e) {
			throw new SAXException("Configuration exception occurred while enabling namespace support", e);
			//Generalize Exception that should never occur
		}

		List<XmlTag> ret = xmlDoc;

		// prepare for reuse;
		this.xmlDoc = null;
		this.xmlStack = null;

		return ret;
	}

	// START PRIVATE CLASSES / METHODS ////////////////////////////////////////
	private static Map<String, String> decodeAttrs(Attributes attrs) {
		Map<String, String> ret = new HashMap<String, String>();
		for (int i = 0; i <= attrs.getLength() - 1; i++) {
			ret.put(attrs.getQName(i), attrs.getValue(i));
		}
		return ret;
	}

	private class XmlParserHandler extends DefaultHandler {
		// private final String LOG_TAG = this.getClass().getName();

		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {
			XmlTag curTag = new XmlTag();
			if (xmlStack.size() == 0) {// root tag
				xmlDoc.add(curTag);
			} else {
				XmlTag parentTag = xmlStack.get(xmlStack.size() - 1);
				parentTag.enclosedTags.add(curTag);
			}

			xmlStack.add(curTag);

			curTag.tagName = qName;
			curTag.attributes = decodeAttrs(attributes);
		}

		@Override
		public void characters(char buf[], int offset, int len)
				throws SAXException {
			String text = String.copyValueOf(buf, offset, len);
			XmlTag curTag = xmlStack.get(xmlStack.size() - 1);
			curTag.text += text;
		}

		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			XmlTag curTag = xmlStack.remove(xmlStack.size() - 1);
			curTag.trimStrings(); // remove pesky CR's and LF's
		}
	}
}